package it.polito.let.java;

import it.polito.let.util.Hex;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JavaClass {

	private Set<String> importList = new HashSet<String>();
	private List<String> suffixList = new ArrayList<String>();
	private JavaMethod main = null;
	private String[] modifiers = {"public"};
	private String name;
	private Map<String, JavaMethod> methods = new HashMap<String, JavaMethod>();
	private Map<String, Serializable> serAttr = new HashMap<String,Serializable>();
	private Map<String, String> attributes = new HashMap<String,String>();
	private JavaMethod unpackMethod;
	private StringBuffer globalStatement = new StringBuffer();

	protected JavaClass(String name) {
		this(name, false);
	}

	protected JavaClass(String name, boolean putMain) {
		this.name = name;
		if (putMain) {
			try {
				main = addMethod(new String[] { "public", "static" },"void", "main", new String[] { "String[] params" });
			} catch (DuplicateMethodException e) {
			}
		}
		unpackMethod = new JavaMethod(new String[]{"private","static"},"Serializable","UNPACK",new String[]{"String s"});
		unpackMethod.appendStatement
		(		"try {\n"
				+"byte[] buf = Hex.toByteArr(s);\n"
				+"ByteArrayInputStream bis = new ByteArrayInputStream(buf);\n"
				+"ObjectInputStream ois = new ObjectInputStream(bis);\n"
				+"return (Serializable) ois.readObject();\n"
				+"} catch (Exception e) {\n"
				+"return null;\n"
				+"}"	);
	}

	public String getName() {
		return name;
	}

	public void addImport(String imp) {
		importList.add(imp);
	}

	public void addSuffix(String suff) {
		suffixList.add(suff);
	}


	public JavaMethod addMethod(String[] modifiers, String type, String name, String[] params) throws DuplicateMethodException {
		JavaMethod newMethod;
		if (methods.containsKey(JavaMethod.toID(name, params)))
			throw new DuplicateMethodException(JavaMethod.toID(name, params));
		newMethod = new JavaMethod(modifiers, type, name, params);
		methods.put(JavaMethod.toID(name, params), newMethod);
		return newMethod;
	}

	public JavaMethod getMethod(String name, String[] params) {
		if (!methods.containsKey(JavaMethod.toID(name, params)))
			return null;
		return methods.get(JavaMethod.toID(name, params));
	}

	public void addSerAttribute (String name, Serializable obj) throws DuplicateAttributeException, DuplicateMethodException {
		if (serAttr.containsKey(name))
			throw new DuplicateAttributeException(name);
		serAttr.put(name,obj);
		addMethod(new String[]{"public"}, obj.getClass().getName(), "get"+name.substring(0,1).toUpperCase()+name.substring(1), null).appendStatement("return "+name+";");

	}	
	public void addAttribute(String type, String name, String value) throws DuplicateAttributeException {
		if (serAttr.containsKey(name))
			throw new DuplicateAttributeException(name);
		if (attributes.containsKey(name))
			throw new DuplicateAttributeException(name);
		String attrval = type+" "+name;
		if (value != null)
			attrval += " = "+value;
		attrval += ";";
		attributes.put(name, attrval);
	}

	public JavaMethod getMain() {
		return main;
	}

	public static String dumpAttribute (Serializable obj) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream obj_out = new ObjectOutputStream(bos);
		obj_out.writeObject(obj);
		obj_out.close();
		return Hex.toHex(bos.toByteArray());
	}

	public String toString() {
		StringBuffer result = new StringBuffer();
		for (String i:importList) {
			result.append("import "+i+";\n\n");
		}
		for (String m:modifiers)
			result.append(m+" ");
		result.append("class "+name+" ");
		for (String s:suffixList)
			result.append(s+" ");
		result.append("{\n");
		for (String att:attributes.values()) {
			result.append(att+"\n");
		}
		for (String a:serAttr.keySet()) {
			Serializable o = serAttr.get(a);
			result.append("private "+o.getClass().getName()+" "+a
					+" = ("+o.getClass().getName()+") UNPACK(\"");
			try {
				result.append(dumpAttribute(o));
			} catch (IOException e) {
			}
			result.append("\");\n");
		}
		result.append(globalStatement+"\r\n");
		for (JavaMethod m:methods.values()) {
			result.append(m.toString());
		}
		if (serAttr.size()>0)
			result.append(unpackMethod);
		result.append("}\n\n");
		return result.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof JavaClass))
			return false;
		JavaClass other = (JavaClass) obj;
		if (getName() == null) {
			if (other.getName() != null)
				return false;
		} else if (!getName().equals(other.getName()))
			return false;
		return true;
	}

	public void appendStatement(String s) {
		globalStatement.append(s);	
	}

}
